<?php

namespace yifang\container;

use Closure;
use Exception;
use yifang\container\concern\Reflection;
use ArrayAccess;
use Countable;
use IteratorAggregate;
use ArrayIterator;

class Container implements ArrayAccess,Countable,IteratorAggregate
{
    use Reflection;

    /**
     * 容器对象实例
     * @var Container|Closure
     */
    protected static $instance;

    /**
     * 容器中的对象实例
     * @var array
     */
    protected $instances = [];

    /**
     * 容器绑定标识
     * @var array
     */
    protected $bind = [];

    /**
     * 容器回调
     * @var array
     */
    protected $invokeCallback = [];

    /**
     * 获取当前容器的实例（单例）
     * @access public
     * @return static
     */
    public static function getInstance()
    {
        if (is_null(static::$instance)) {
            static::$instance = new static;
        }
        if (static::$instance instanceof Closure) {
            return (static::$instance)();
        }
        return static::$instance;
    }

    /**
     * 设置当前容器的实例
     * @access public
     * @param object|Closure $instance
     */
    public static function setInstance($instance)
    {
        static::$instance = $instance;
    }

    /**
     * 根据别名获取真实类名
     * @param  string $abstract
     * @return string
     */
    public function getAlias($abstract)
    {
        if (isset($this->bind[$abstract])) {
            $bind = $this->bind[$abstract];
            if (is_string($bind)) {
                return $this->getAlias($bind);
            }
        }
        return $abstract;
    }

    /**
     * 注册一个容器对象回调
     *
     * @param string|Closure $abstract
     * @param Closure|null   $callback
     */
    public function resolving($abstract, Closure $callback = null)
    {
        if ($abstract instanceof Closure) {
            $this->invokeCallback['*'][] = $abstract;
            return;
        }
        $abstract = $this->getAlias($abstract);
        $this->invokeCallback[$abstract][] = $callback;
    }

    /**
     * 获取容器中的对象实例 不存在则创建
     */
    public static function pull($abstract, array $vars = [],$newInstance = false)
    {
        return static::getInstance()->make($abstract, $vars, $newInstance);
    }

    /**
     * 创建类的实例 已经存在则直接获取
     * @param string|class-string<T> $abstract    类名或者标识
     * @param array                  $vars        变量
     * @param bool                   $newInstance 是否每次创建新的实例
     */
    public function make($abstract, array $vars = [], $newInstance = false)
    {
        $abstract = $this->getAlias($abstract);
        if (isset($this->instances[$abstract]) && !$newInstance) {
            return $this->instances[$abstract];
        }
        if (isset($this->bind[$abstract])) {
            if ($this->bind[$abstract] instanceof Closure) {
                $object = $this->invokeFunction($this->bind[$abstract], $vars);
            } else {
                $object = $this->invokeClass($this->bind[$abstract], $vars);
            }
        }else{
            $object = $this->invokeClass($abstract, $vars);
        }
        if (!$newInstance) {
            $this->instances[$abstract] = $object;
        }
        return $object;
    }

    /**
     * 获取容器中的对象实例
     */
    public function get($abstract)
    {
        if ($this->has($abstract)) {
            return $this->make($abstract);
        }
        throw new Exception('class not exists: ' . $abstract, 1);
    }

    /**
     * 判断容器中是否存在类及标识
     */
    public function bound($abstract)
    {
        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
    }

    /**
     * 判断容器中是否存在类及标识
     */
    public function has($name)
    {
        return $this->bound($name);
    }

    /**
     * 绑定一个类、闭包、实例、接口实现到容器
     * @param string|array $abstract 类标识、接口
     * @param mixed        $concrete 要绑定的类、闭包或者实例
     */
    public function bind($abstract, $concrete = null)
    {
        if (is_array($abstract)) {
            foreach ($abstract as $key => $val) {
                $this->bind($key, $val);
            }
        } elseif ($concrete instanceof Closure) {
            $this->bind[$abstract] = $concrete;
        } elseif (is_object($concrete)) {
            $this->instance($abstract, $concrete);
        } else {
            $abstract = $this->getAlias($abstract);
            if ($abstract != $concrete) {
                $this->bind[$abstract] = $concrete;
            }
        }
        return $this;
    }

    /**
     * 绑定一个类实例到容器
     * @param string $abstract 类名或者标识
     * @param object $instance 类的实例
     */
    public function instance($abstract, $instance)
    {
        $abstract = $this->getAlias($abstract);
        $this->instances[$abstract] = $instance;
        return $this;
    }

    /**
     * 删除容器中的对象实例
     * @access public
     * @param string $name 类名或者标识
     */
    public function delete($name)
    {
        $name = $this->getAlias($name);
        if (isset($this->instances[$name])) {
            unset($this->instances[$name]);
        }
    }

    /**
     * 判断容器中是否存在对象实例
     * @access public
     * @param string $abstract 类名或者标识
     * @return bool
     */
    public function exists($abstract)
    {
        $abstract = $this->getAlias($abstract);
        return isset($this->instances[$abstract]);
    }

    public function __set($name, $value)
    {
        $this->bind($name, $value);
    }

    public function __get($name)
    {
        return $this->get($name);
    }

    public function __isset($name)
    {
        return $this->exists($name);
    }

    public function __unset($name)
    {
        $this->delete($name);
    }

    #[\ReturnTypeWillChange]
    public function offsetExists($key)
    {
        return $this->exists($key);
    }

    #[\ReturnTypeWillChange]
    public function offsetGet($key)
    {
        return $this->make($key);
    }

    #[\ReturnTypeWillChange]
    public function offsetSet($key, $value)
    {
        $this->bind($key, $value);
    }

    #[\ReturnTypeWillChange]
    public function offsetUnset($key)
    {
        $this->delete($key);
    }

    //Countable
    public function count()
    {
        return count($this->instances);
    }

    public function getIterator()
    {
        return new ArrayIterator($this->instances);
    }
}